﻿#region Namespaces

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security;
using System.Text;
using System.Xml;
using System.Xml.XPath;
using System.Xml.Xsl;

using Vibstudio.X4NET.Xml.Linq;

#endregion

namespace Vibstudio.X4NET.Xml
{
    public static class XmlExtensions
    {
        public static string InnerXml(this XElement element)
        {
            StringBuilder innerXml = new StringBuilder();

            foreach (XNode node in element.Nodes())
            {
                innerXml.Append(node);
            }

            return innerXml.ToString();
        }

        public static bool IsWellFormedXml(this string xml)
        {
            try
            {
                XmlDocument document = new XmlDocument();
                document.LoadXml(xml);
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        public static string EscapeXml(this XPathNavigator navigator)
        {
            return SecurityElement.Escape(navigator.Value);
        }

        public static int IndexPosition(this XElement element)
        {
            if (element == null)
            {
                throw new ArgumentNullException("element");
            }

            if (element.Parent == null)
            {
                return -1;
            }

            int i = 1;

            foreach (var sibling in element.Parent.Elements(element.Name))
            {
                if (sibling == element)
                {
                    return i;
                }
                i++;
            }

            throw new InvalidOperationException("Element has been removed from its parent.");
        }

        public static XmlDocument ToXmlDocument(this XElement element)
        {
            return element.InnerXml().ToXmlDocument();
        }

        public static XmlDocument ToXmlDocument(this byte[] buffer)
        {
            XmlDocument document = new XmlDocument();
            using (MemoryStream ms = new MemoryStream(buffer))
            {
                document.Load(ms);
            }

            return document;
        }

        public static string RootName(this XDocument document)
        {
            XElement element = document.Descendants().FirstOrDefault();
            return element != null ? element.Name.LocalName : "";
        }

        public static XmlDocument ToXmlDocument(this XDocument xDocument)
        {
            XmlDocument xmlDocument = new XmlDocument();
            using (XmlReader reader = xDocument.CreateReader())
            {
                xmlDocument.Load(reader);
            }
            return xmlDocument;
        }

        public static XmlDocument ToXmlDocument(this string xml)
        {
            XmlDocument document = new XmlDocument();
            document.LoadXml(xml);
            return document;
        }

        public static XmlDocument FormatXml(this XmlDocument document, bool omitXmlDeclaration = false, bool stripEmptyNodes = false)
        {
            if (stripEmptyNodes)
            {
                StripEmptyNodes(ref document);
            }

            XmlWriterSettings settings =
                new XmlWriterSettings
                {
                    Indent = true,
                    OmitXmlDeclaration = omitXmlDeclaration,
                    IndentChars = @"    ",
                    NewLineChars = Environment.NewLine,
                    NewLineHandling = NewLineHandling.Replace,
                };

            StringBuilder builder = new StringBuilder();
            using (var writer = XmlWriter.Create(builder, settings))
            {
                if (document.ChildNodes[0] is XmlProcessingInstruction)
                {
                    document.RemoveChild(document.ChildNodes[0]);
                }

                document.Save(writer);

                XmlDocument formattedDocument = new XmlDocument { PreserveWhitespace = true };
                formattedDocument.LoadXml(builder.ToString());

                return formattedDocument;
            }
        }
        
        public static XDocument ToXDocument(this XmlDocument document)
        {
            using (XmlReader reader = new XmlNodeReader(document))
            {
                reader.MoveToContent();
                return XDocument.Load(reader);
            }
        }

        public static XDocument ToXDocument(this string xml)
        {
            return xml.ToXmlDocument().ToXDocument();
        }

        public static XDocument ToXDocument(this XElement element)
        {
            return XDocument.Load(element.CreateReader());
        }

        public static XDocument ToXDocument(this XContainer container)
        {
            return ((XElement)(container)).ToXDocument();
        }

        public static XDocument ToXDocument(this byte[] buffer)
        {
            return buffer.ToXmlDocument().ToXDocument();
        }
        
        public static string ToString(this XmlDocument document)
        {
            using (StringWriter sw = new StringWriter())
            {
                using (XmlTextWriter tx = new XmlTextWriter(sw))
                {
                    document.WriteTo(tx);
                    string strXmlText = sw.ToString();
                    return strXmlText;
                }
            }
        }

        internal static string EscapeCsv(this string data)
        {
            const string ESCAPE_CHAR = " ";

            IDictionary<string, string> escapes = new Dictionary<string, string>();
            escapes.Add(";", ESCAPE_CHAR);

            return escapes.Aggregate(data, (current, pair) => current.Replace(pair.Key, pair.Value));
        }

        #region Helpers

        private static void StripEmptyNodes(ref XmlDocument document)
        {
            using (StringWriter writer = new StringWriter())
            {
                using (XmlReader stylesheetReader = XmlReader.Create(new StringReader(XResources.XmlStripper)))
                {
                    using (XmlReader documentReader = new XmlNodeReader(document))
                    {
                        XslCompiledTransform transformer = new XslCompiledTransform(false);
                        transformer.Load(stylesheetReader);
                        transformer.Transform(documentReader, null, writer);
                    }
                }

                document.LoadXml(writer.ToString());
            }
        }

        #endregion
    }
}